﻿namespace CacheProvider
{
    #region N A M E S P A C E   I M P O R T S

    using System;
    using System.Collections.Generic;
    using System.Configuration.Provider;
    using System.Diagnostics;
    using System.Reflection;
    using Microsoft.Practices.Composite.Events;
    using System.Threading;
    using System.Management;
    using System.Configuration;
    using CacheProviderInterfaces;
    using DIContainerProvider;
    using CacheProviderEntities;
    using Microsoft.Practices.Composite.Presentation.Events;

    #endregion

    public abstract class CacheProvider : ProviderBase
    {
        private ILogWriter _logWriter;
        protected string _dependencyTrackerCollectionKey = "AppFabricCacheProvider_DependencyTrackers";

        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            base.Initialize(name, config);

            //Set whether logging enabled
            this.BootstrapContainer();
            this.LoggingCategory = ConfigurationManager.AppSettings["ApplicationName"];
            this._logWriter = DIContainer.Resolve<ILogWriter>();
            Debug.Assert(this.DIContainer != null, "DIContainer Must be set here!");
            //Set the dependency tracker cache key
            this.InitializeDependencyTrackerKey();
        }

        #region P R O P E R T I E S

        public abstract long DefaultExpirationTimeInMilliSeconds { get; set; }

        public abstract bool IsCacheTagSupported { get; }

        public abstract bool IsRegionSupported { get; }

        public abstract bool IsConcurrencySupported { get; }

        public abstract bool IsNamedCacheSupported { get; }

        public abstract string CacheName { get; }

        public abstract Int16 DataCacheFactoryPoolSize { get; }

        public abstract bool EagerPoolingEnabled { get; }

        public abstract bool IsCacheItemVersioningSupported { get; }

        public IEventAggregator EventAggregatorInstance
        {
            get
            {
                return this.DIContainer.Resolve<IEventAggregator>();
            }
        }

        private IDependencyInjectionContainer _diContainer;
        public IDependencyInjectionContainer DIContainer
        {
            get
            {
                if (this._diContainer == null)
                    this.InitializeContainer();
                return _diContainer;
            }
            set
            {
                this._diContainer = value;
            }
        }

        public ILogWriter LogWriter
        {
            get
            {
                return this._logWriter;
            }
        }

        public string CacheClientNodeIdentifier { get; private set; }

        public string LoggingCategory { get; set; }

        #endregion

        #region A D D   O V E R L O A D S

        public abstract bool Add(string key, object value);

        public abstract bool Add(string key, object value, TimeSpan timeout);

        public abstract bool Add(string key, object value, bool UseDefaultExpiration);

        public abstract bool Add(string key, object value, long ttlInMilliSeconds);

        #region A P P F A B R I C   S P E C I F I C    A D D   O V E R L O A D S

        public bool Add(string key, object value, string regionName)
        {
            if (IsRegionSupported)
                return AddImpl(key, value, regionName);

            throw new NotSupportedException(string.Format("Regions are not supported in this Provider {0}", this.Name));

        }

        protected virtual bool AddImpl(string key, object value, string regionName)
        {
            return false;
        }

        /// <summary>
        /// Adds an object to the cache.
        /// If an item using the same key is already present in the cache, this call throws an exception of type 
        /// <see cref="DataCacheException"/> with the ErrorCode set to KeyAlreadyExists.
        /// Expiration settings are derived from the named cache configuration
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="cacheTags">Tags that can be associated with the object to be Cached.</param>
        /// <returns>True if successful else false</returns>
        /// <remarks>
        /// Cache Tags are supported only for objects that are stored in a region.  
        /// Hence, Retrieving by Tags is not possible for objects stored without a region but with Cache Tags.
        /// </remarks>
        public bool Add(string key, object value, IEnumerable<String> cacheTags)
        {
            if (IsCacheTagSupported)
                return AddImpl(key, value, cacheTags);

            throw new NotSupportedException(string.Format("Cache Tags are not supported in this Provider {0}", this.Name));

        }

        protected virtual bool AddImpl(string key, object value, IEnumerable<String> cacheTags)
        {
            return false;
        }

        public bool Add(string key, object value, IEnumerable<String> cacheTags, string regionName)
        {
            if (IsCacheTagSupported && IsRegionSupported)
                return AddImpl(key, value, cacheTags, regionName);

            throw new NotSupportedException(string.Format("Regions & Cache Tags are not supported in this Provider {0}", this.Name));
        }

        protected virtual bool AddImpl(string key, object value, IEnumerable<String> cacheTags, string regionName)
        {
            return false;
        }

        /// <summary>
        /// Adds an object to the cache.
        /// If an item using the same key is already present in the cache, this call throws an exception
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="timeout">Expiration time for the cache entry</param>
        /// <param name="cacheTags">Represents an List of string-based identifiers that can be associated with a cached object.
        /// Tags may only be used to retrieve a cached object if that object is stored in a region. This overload does not store the object in a region.</param>
        /// <returns>True if successful else false</returns>
        public bool Add(string key, object value, TimeSpan timeout, IEnumerable<String> cacheTags)
        {
            if (IsCacheTagSupported)
                return AddImpl(key, value, timeout, cacheTags);

            throw new NotSupportedException(string.Format("Cache Tags are not supported in this Provider {0}", this.Name));
        }

        protected virtual bool AddImpl(string key, object value, TimeSpan timeout, IEnumerable<String> cacheTags)
        {
            return false;
        }

        public bool Add(string key, object value, TimeSpan timeout, string regionName)
        {
            if (IsRegionSupported)
                return this.AddImpl(key, value, timeout, regionName);

            throw new NotSupportedException(string.Format("Regions are not supported in this Provider {0}", this.Name));

        }

        protected virtual bool AddImpl(string key, object value, TimeSpan timeout, string regionName)
        {
            return false;
        }

        public bool Add(string key, object value, TimeSpan timeout, IEnumerable<String> cacheTags, string regionName)
        {
            if (IsCacheTagSupported && IsRegionSupported)
                return this.AddImpl(key, value, timeout, cacheTags, regionName);

            throw new NotSupportedException(string.Format("Regions & Cache Tags are not supported in this Provider {0}", this.Name));
        }

        protected virtual bool AddImpl(string key, object value, TimeSpan timeout, IEnumerable<String> cacheTags, string regionName)
        {
            return false;
        }

        #endregion

        #endregion

        #region G E T   O V E R L O A D S

        public abstract object Get(string key);

        public abstract T Get<T>(string key);

        public abstract IDictionary<string, object> Get(params string[] keys);

        public bool IsCacheVersionSame(object earlierVersion, string key, string regionName)
        {
            if (!this.IsCacheItemVersioningSupported)
                throw new NotSupportedException(string.Format("Cache Version is not supported in this Provider {0}", this.Name));
            if (!this.IsRegionSupported)
                throw new NotSupportedException(string.Format("Regions are not supported in this Provider {0}", this.Name));

            return this.IsCacheVersionSameImpl(earlierVersion, key, regionName);
        }

        protected virtual bool IsCacheVersionSameImpl(object earlierVersion, string key, string regionName)
        {
            return false;
        }

        public bool IsCacheVersionSame(object firstDataCacheVersion, object secondDataCacheVersion)
        {
            return this.IsCacheVersionSame(firstDataCacheVersion, secondDataCacheVersion);
        }

        protected virtual bool IsCacheVersionSameImpl(object firstDataCacheVersion, object secondDataCacheVersion)
        {
            return true;
        }

        #region A P P F A B R I C   S P E C I F I C   G E T   O V E R L O A D S

        public object Get(string key, string regionName)
        {
            if (IsRegionSupported)
                if (string.IsNullOrEmpty(regionName))
                    throw new ArgumentException("regionName");
                else
                    return this.GetImpl(key, regionName);

            throw new NotSupportedException(string.Format("Regions are not supported in this Provider {0}", this.Name));
        }

        protected virtual object GetImpl(string key, string regionName)
        {
            return false;
        }

        public object GetAndLock(string key, TimeSpan timeout, out Guid lockId)
        {
            if (IsConcurrencySupported)
                return this.GetAndLockImpl(key, timeout, out lockId);

            throw new NotSupportedException(string.Format("Concurrency is not supported in this Provider {0}", this.Name));
        }

        protected virtual object GetAndLockImpl(string key, TimeSpan timeout, out Guid lockId)
        {
            lockId = Guid.Empty;
            return null;
        }

        public object GetAndLock(string key, TimeSpan timeout, out Guid lockId, bool forceLock)
        {
            if (IsConcurrencySupported)
                return this.GetAndLockImpl(key, timeout, out lockId, forceLock);

            throw new NotSupportedException(string.Format("Concurrency is not supported in this Provider {0}", this.Name));
        }

        protected virtual object GetAndLockImpl(string key, TimeSpan timeout, out Guid lockId, bool forceLock)
        {
            lockId = Guid.Empty;
            return null;
        }

        /// <summary>
        /// Locks the key if the key is present and is not locked and returns the object corresponding to the key. For objects stored in regions.
        /// </summary>
        /// <param name="key">The unique value that is used to identify the object in the region.</param>
        /// <param name="timeout">The amount of time that the object remains locked.</param>
        /// <param name="lockId">The GUID required to unlock the object. It is an output parameter</param>
        /// <param name="regionName">The name of the region where the object resides</param>
        /// <returns>Returns Object matching the specified key parameter if the Object is present and is not locked. 
        /// If the key does not exist, a DataCacheException object is thrown with the ErrorCode set to KeyDoesNotExist. Create objects based on the referenced key to resolve this error.
        /// If the object is already locked by another cache client, a DataCacheException object is thrown with the ErrorCode set to ObjectLocked. The object will be inaccessible until it is unlocked by the locking client.
        /// </returns>
        /// <remarks>
        /// Warning  
        /// Locked objects in the cache can still be replaced by any cache client with the Put method. 
        /// Cache-enabled applications are responsible for consistently using PutAndUnlock for items 
        /// that use the pessimistic concurrency model. 
        /// </remarks>
        public object GetAndLock(string key, TimeSpan timeout, out Guid lockId, string regionName)
        {
            if (IsConcurrencySupported && IsRegionSupported)
                return this.GetAndLockImpl(key, timeout, out lockId, regionName);

            throw new NotSupportedException(string.Format("Concurrency && Regions are not supported in this Provider {0}", this.Name));

        }

        protected virtual object GetAndLockImpl(string key, TimeSpan timeout, out Guid lockId, string regionName)
        {
            lockId = Guid.Empty;
            return null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <param name="timeout"></param>
        /// <param name="lockId"></param>
        /// <param name="regionName"></param>
        /// <param name="forceLock">If forceLock is true, key is locked irrespective of key-value pair presence in cache</param>
        /// <returns></returns>
        public object GetAndLock(string key, TimeSpan timeout, out Guid lockId, string regionName, bool forceLock)
        {
            if (IsRegionSupported && IsConcurrencySupported)
                return this.GetAndLockImpl(key, timeout, out lockId, regionName, forceLock);

            throw new NotSupportedException(string.Format("Concurrency && Regions are not supported in this Provider {0}", this.Name));
        }

        protected virtual object GetAndLockImpl(string key, TimeSpan timeout, out Guid lockId, string regionName, bool forceLock)
        {
            lockId = Guid.Empty;
            return null;

        }

        public IEnumerable<KeyValuePair<string, object>> GetObjectsByAllTags(IEnumerable<string> cacheTags, string regionName)
        {
            if (this.IsCacheTagSupported && this.IsRegionSupported)
                return this.GetObjectsByAllTagsImpl(cacheTags, regionName);

            throw new NotSupportedException(string.Format("Cache Tages & Regions are not supported in this Provider {0}", this.Name));
        }

        protected virtual IEnumerable<KeyValuePair<string, object>> GetObjectsByAllTagsImpl(IEnumerable<string> cacheTags, string regionName)
        {
            return null;
        }

        public IEnumerable<KeyValuePair<string, object>> GetObjectsByAnyTag(IEnumerable<string> cacheTags, string regionName)
        {
            if (this.IsCacheTagSupported && this.IsRegionSupported)
                return this.GetObjectsByAnyTagImpl(cacheTags, regionName);

            throw new NotSupportedException(string.Format("Cache Tages & Regions are not supported in this Provider {0}", this.Name));
        }

        protected virtual IEnumerable<KeyValuePair<string, object>> GetObjectsByAnyTagImpl(IEnumerable<string> cacheTags, string regionName)
        {
            return null;
        }

        public IEnumerable<KeyValuePair<string, object>> GetObjectsByTag(string cacheTag, string regionName)
        {
            if (this.IsCacheTagSupported && this.IsRegionSupported)
                return this.GetObjectsByTagImpl(cacheTag, regionName);

            throw new NotSupportedException(string.Format("Cache Tages & Regions are not supported in this Provider {0}", this.Name));
        }

        protected virtual IEnumerable<KeyValuePair<string, object>> GetObjectsByTagImpl(string cacheTag, string regionName)
        {
            return null;
        }

        public IEnumerable<KeyValuePair<string, object>> GetObjectsInRegion(string regionName)
        {
            if (IsRegionSupported)
                return this.GetObjectsInRegionImpl(regionName);

            throw new NotSupportedException(string.Format("Regions are not supported in this Provider {0}", this.Name));
        }

        protected virtual IEnumerable<KeyValuePair<string, object>> GetObjectsInRegionImpl(string regionName)
        {
            return null;
        }

        public string GetSystemRegionName(string key)
        {
            if (IsRegionSupported)
                return this.GetSystemRegionNameImpl(key);

            throw new NotSupportedException(string.Format("Regions are not supported in this Provider {0}", this.Name));
        }

        protected virtual string GetSystemRegionNameImpl(string key)
        {
            return null;
        }

        public IEnumerable<string> GetSystemRegions()
        {
            if (IsRegionSupported)
                return this.GetSystemRegionsImpl();

            throw new NotSupportedException(string.Format("Regions are not supported in this Provider {0}", this.Name));
        }

        protected virtual IEnumerable<string> GetSystemRegionsImpl()
        {
            return null;
        }

        #endregion

        #endregion

        #region P U T   O V E R L O A D S

        public abstract bool Put(string key, object value);
        public abstract bool Put(string key, object value, bool useDefaultTimeout);


        #region A P P F A B R I C   S P E C I F I C  P U T  O V E R L O A D S

        /// <summary>
        /// Adds or replaces an object in the cache
        /// </summary>
        /// <param name="key">The unique value that is used to identify the object in the region</param>
        /// <param name="value">The object to add or replace</param>
        /// <param name="regionName">The name of the region the object resides in</param>
        /// <returns>True if successful. Throws NotSupportedException if Regions are not supported by a provider.</returns>
        public bool Put(string key, object value, string regionName)
        {
            if (IsRegionSupported)
                return this.PutImpl(key, value, regionName);

            throw new NotSupportedException(string.Format("Regions are not supported in this Provider {0}", this.Name));

        }

        protected virtual bool PutImpl(string key, object value, string regionName)
        {
            return false;
        }


        public bool Put(string key, object value, IEnumerable<String> cacheTags)
        {
            if (IsCacheTagSupported)
                return this.PutImpl(key, value, cacheTags);

            throw new NotSupportedException(string.Format("Cache Tags are not supported in this Provider {0}", this.Name));

        }

        protected virtual bool PutImpl(string key, object value, IEnumerable<String> cacheTags)
        {
            return false;
        }

        public abstract bool Put(string key, object value, TimeSpan timeout);

        public bool Put(string key, object value, IEnumerable<String> cacheTags, string regionName)
        {
            if (IsCacheTagSupported && IsRegionSupported)
                return this.PutImpl(key, value, cacheTags, regionName);

            throw new NotSupportedException(string.Format("Regions & Cache Tags are not supported in this Provider {0}", this.Name));
        }

        protected virtual bool PutImpl(string key, object value, IEnumerable<String> cacheTags, string regionName)
        {
            return false;
        }

        public bool Put(string key, object value, TimeSpan timeout, IEnumerable<String> cacheTags)
        {
            if (IsCacheTagSupported)
                return this.PutImpl(key, value, timeout, cacheTags);

            throw new NotSupportedException(string.Format("Cache Tags are not supported in this Provider {0}", this.Name));
        }

        protected virtual bool PutImpl(string key, object value, TimeSpan timeout, IEnumerable<String> cacheTags)
        {
            return false;
        }

        public bool Put(string key, object value, TimeSpan timeout, string regionName)
        {
            if (IsRegionSupported)
                return this.PutImpl(key, value, timeout, regionName);

            throw new NotSupportedException(string.Format("Regions are not supported in this Provider {0}", this.Name));
        }

        protected virtual bool PutImpl(string key, object value, TimeSpan timeout, string regionName)
        {
            return false;
        }

        public bool Put(string key, object value, TimeSpan timeout, IEnumerable<String> cacheTags, string regionName)
        {
            if (IsRegionSupported && IsCacheTagSupported)
                return this.PutImpl(key, value, timeout, cacheTags, regionName);

            throw new NotSupportedException(string.Format("Regions & Cache Tags are not supported in this Provider {0}", this.Name));

        }

        protected virtual bool PutImpl(string key, object value, TimeSpan timeout, IEnumerable<String> cacheTags, string regionName)
        {
            return false;
        }

        #endregion

        #region P U T   W I T H   C A C H E D E P E N D E N C Y  O V E R L O A D S

        public abstract bool Put(string key, Object value, DependencyInfo dependencyInfo);

        public abstract bool Put(string key, Object value, DependencyInfo dependencyInfo, TimeSpan timeout);

        /// <summary>
        /// Adds or updates a cache entry.
        /// </summary>
        /// <typeparam name="T">Type of Dependency callback payload</typeparam>
        /// <param name="key">Unique string to identify this entry in Cache.</param>
        /// <param name="value">Value associated with the key.  Should be annotated with <see cref="SerializableAttribute"/></param>
        /// <param name="regionName">Name of the region where the key\value will be stored if supported by the provider. <seealso cref="IsRegionSupported"/>  Pass null if not required or not supported.</param>
        /// <param name="timeout">Time-to-Live for this entry in Cache.</param>
        /// <param name="cacheTags">Tags associated with the cache if supported by the provider. <seealso cref="IsCacheTagSupported"/> Pass null if not required or not supported.</param>
        /// <param name="dependencyInfo">An instance of <see cref="DependencyInfo"/> </param>
        /// <param name="cacheUpdationCallback">Delegate to the method which will be invoked if the dependency changes.</param>
        /// <param name="callbackState">Instance of T that will be passed as parameter to the callback.</param>
        /// <param name="threadOption">Thread on which the callback will be invoked. <see cref="ThreadOption"/></param>
        /// <param name="keepSubscriberReferenceAlive">Whether to hold a weak or a strong reference to the callback.</param>
        /// <returns>True if successful else false.</returns>
        public bool Put<T>(string key, Object value, string regionName, TimeSpan timeout, IEnumerable<String> cacheTags,
                           DependencyInfo dependencyInfo, Action<T> cacheUpdationCallback, T callbackState,
                           ThreadOption threadOption, bool keepSubscriberReferenceAlive)
        {
            if (!string.IsNullOrEmpty(regionName) && !this.IsRegionSupported)
                throw new NotSupportedException();

            if (cacheTags != null && !this.IsCacheTagSupported)
                throw new NotSupportedException();

            var result = this.PutImpl(key, value, regionName, timeout, cacheTags, dependencyInfo, cacheUpdationCallback, callbackState, threadOption, keepSubscriberReferenceAlive);
            var message = string.Format("Cache with key {0} {1} to namedCache {2} under regionName {3}", key,
                (result) ? "added" : "not added", this.CacheName, regionName);
            if (result)
                this.LogWriter.Write(message, this.LoggingCategory, 1, (int)LoggingEvent.CachePut, TraceEventType.Information, key);
            else
                this.LogWriter.Write(message, this.LoggingCategory, 1, (int)LoggingEvent.CachePut, TraceEventType.Error, key);

            return result;
        }

        protected virtual bool PutImpl<T>(string key, Object value, string regionName, TimeSpan timeout,
                                          IEnumerable<String> cacheTags, DependencyInfo dependencyInfo,
                                          Action<T> cacheUpdationCallback, T callbackState,
                                          ThreadOption threadOption, bool keepSubscriberReferenceAlive)
        {
            return false;
        }


        public bool Put<T>(string key, Object value, string regionName, bool useDefaultTimeout, IEnumerable<String> cacheTags,
                           DependencyInfo dependencyInfo, Action<T> cacheUpdationCallback, T callbackState,
                           ThreadOption threadOption, bool keepSubscriberReferenceAlive)
        {
            if (!string.IsNullOrEmpty(regionName) && !this.IsRegionSupported)
                throw new NotSupportedException();

            if (cacheTags != null && !this.IsCacheTagSupported)
                throw new NotSupportedException();

            if (!useDefaultTimeout)
                throw new InvalidOperationException("parameter [useDefaultTimeout] must be true for this Put() Overload");

            var result = this.PutImpl(key, value, regionName, useDefaultTimeout, cacheTags, dependencyInfo, cacheUpdationCallback, callbackState, threadOption, keepSubscriberReferenceAlive);
            var message = string.Format("Cache with key {0} {1} to namedCache {2} under regionName {3}", key,
                (result) ? "added" : "not added", this.CacheName, regionName);
            if (result)
                this.LogWriter.Write(message, this.LoggingCategory, 1, (int)LoggingEvent.CachePut, TraceEventType.Information, key);
            else
                this.LogWriter.Write(message, this.LoggingCategory, 1, (int)LoggingEvent.CachePut, TraceEventType.Error, key);

            return result;
        }

        protected virtual bool PutImpl<T>(string key, Object value, string regionName, bool useDefaultTimeout, IEnumerable<String> cacheTags,
                           DependencyInfo dependencyInfo, Action<T> cacheUpdationCallback, T callbackState,
                           ThreadOption threadOption, bool keepSubscriberReferenceAlive)
        {
            return false;
        }


        #region A P P F A B R I C   S P E C I F I C

        public bool Put(string key, Object value, string regionName, DependencyInfo dependencyInfo)
        {
            if (this.IsRegionSupported)
                return this.PutImpl(key, value, regionName, dependencyInfo);

            throw new NotSupportedException("Regions are not supported by this provider");
        }

        protected virtual bool PutImpl(string key, Object value, string regionName, DependencyInfo dependencyInfo)
        {
            return false;
        }

        public bool Put(string key, Object value, string regionName, DependencyInfo dependencyInfo, TimeSpan timeout)
        {
            if (this.IsRegionSupported)
                return this.PutImpl(key, value, regionName, dependencyInfo, timeout);

            throw new NotSupportedException("Regions are not supported by this provider");
        }

        protected virtual bool PutImpl(string key, Object value, string regionName, DependencyInfo dependencyInfo, TimeSpan timeout)
        {
            return false;
        }

        #endregion

        #endregion

        #region A P P F A B R I C   S P E C I F I C   C O N C U R R E N T   P U T A N D U N L O C K   O V E R L O A D S

        public bool PutAndUnlock(string key, object value, Guid lockId)
        {
            if (IsConcurrencySupported)
                return this.PutAndUnlockImpl(key, value, lockId);

            throw new NotSupportedException(string.Format("Concurrency is not supported in this Provider {0}", this.Name));
        }

        protected virtual bool PutAndUnlockImpl(string key, object value, Guid lockId)
        {
            return false;
        }

        public bool PutAndUnlock(string key, object value, Guid lockId, IEnumerable<String> cacheTags)
        {
            if (IsConcurrencySupported && IsCacheTagSupported)
                return this.PutAndUnlockImpl(key, value, lockId, cacheTags);

            throw new NotSupportedException(string.Format("Concurrency & CacheTags are not supported in this Provider {0}", this.Name));
        }

        protected virtual bool PutAndUnlockImpl(string key, object value, Guid lockId, IEnumerable<String> cacheTags)
        {
            return false;
        }

        public bool PutAndUnlock(string key, object value, Guid lockId, string regionName)
        {
            if (IsConcurrencySupported && IsRegionSupported)
                return this.PutAndUnlockImpl(key, value, lockId, regionName);

            throw new NotSupportedException(string.Format("Concurrency & Regions are not supported in this Provider {0}", this.Name));
        }

        protected virtual bool PutAndUnlockImpl(string key, object value, Guid lockId, string regionName)
        {
            return false;
        }

        public bool PutAndUnlock(string key, object value, Guid lockId, TimeSpan timeout)
        {
            if (IsConcurrencySupported)
                return this.PutAndUnlockImpl(key, value, lockId, timeout);

            throw new NotSupportedException(string.Format("Concurrency is not supported in this Provider {0}", this.Name));
        }

        protected virtual bool PutAndUnlockImpl(string key, object value, Guid lockId, TimeSpan timeout)
        {
            return false;
        }

        public bool PutAndUnlock(string key, object value, Guid lockId, IEnumerable<String> cacheTags, string regionName)
        {
            if (IsConcurrencySupported && IsCacheTagSupported && IsRegionSupported)
                return this.PutAndUnlockImpl(key, value, lockId, cacheTags, regionName);

            throw new NotSupportedException(string.Format("Concurrency, Regions & CacheTags are not supported in this Provider {0}", this.Name));
        }

        protected virtual bool PutAndUnlockImpl(string key, object value, Guid lockId, IEnumerable<String> cacheTags, string regionName)
        {
            return false;
        }

        public bool PutAndUnlock(string key, object value, Guid lockId, TimeSpan timeout, IEnumerable<String> cacheTags)
        {
            if (IsConcurrencySupported && IsCacheTagSupported)
                return this.PutAndUnlockImpl(key, value, lockId, timeout, cacheTags);

            throw new NotSupportedException(string.Format("Concurrency & CacheTags are not supported in this Provider {0}", this.Name));
        }

        protected virtual bool PutAndUnlockImpl(string key, object value, Guid lockId, TimeSpan timeout, IEnumerable<String> cacheTags)
        {
            return false;
        }

        public bool PutAndUnlock(string key, object value, Guid lockId, TimeSpan timeout, string regionName)
        {
            if (IsRegionSupported && IsConcurrencySupported)
                return this.PutAndUnlockImpl(key, value, lockId, timeout, regionName);

            throw new NotSupportedException(string.Format("Concurrency & Regions are not supported in this Provider {0}", this.Name));
        }

        protected virtual bool PutAndUnlockImpl(string key, object value, Guid lockId, TimeSpan timeout, string regionName)
        {
            return false;
        }

        public bool PutAndUnlock(string key, object value, Guid lockId, TimeSpan timeout, IEnumerable<String> cacheTags, string regionName)
        {
            if (IsConcurrencySupported && IsRegionSupported && IsCacheTagSupported)
                return this.PutAndUnlockImpl(key, value, lockId, timeout, cacheTags, regionName);

            throw new NotSupportedException(string.Format("Concurrency, Cache Tags & Regions are not supported in this Provider {0}", this.Name));
        }

        protected virtual bool PutAndUnlockImpl(string key, object value, Guid lockId, TimeSpan timeout, IEnumerable<String> cacheTags, string regionName)
        {
            return false;
        }

        #endregion

        #endregion

        #region R E M O V E   O V E R L O A D S

        public abstract bool Remove(string key);

        public abstract void RemoveAll();

        #region A P P F A B R I C  S P E C I F I C   R E M O V E   O V E R L O A D S

        public bool Remove(string key, Guid lockId)
        {
            if (IsConcurrencySupported)
                return this.RemoveImpl(key, lockId);

            throw new NotSupportedException(string.Format("Concurrency is not supported in this Provider {0}", this.Name));
        }

        protected virtual bool RemoveImpl(string key, Guid lockId)
        {
            return false;
        }

        public bool Remove(string key, string regionName)
        {
            if (IsConcurrencySupported && IsRegionSupported)
                return RemoveImpl(key, regionName);

            throw new NotSupportedException(string.Format("Concurrency & Regions are not supported in this Provider {0}", this.Name));
        }

        protected virtual bool RemoveImpl(string key, string regionName)
        {
            return false;
        }

        public bool Remove(string key, Guid lockId, string regionName)
        {
            if (IsConcurrencySupported && IsRegionSupported)
                return this.RemoveImpl(key, lockId, regionName);

            throw new NotSupportedException(string.Format("Concurrency & Regions are not supported in this Provider {0}", this.Name));
        }

        protected virtual bool RemoveImpl(string key, Guid lockId, string regionName)
        {
            return false;
        }

        #endregion

        #endregion

        #region A P P F A B R I C  R E G I O N   M A N A G E M E N T   M E T H O D S

        /// <summary>
        /// Creates a region.
        /// </summary>
        /// <param name="regionName">Represents a region name.Region name should contain only alphanumeric characters</param>
        /// <returns>True if region is created.  False if region already exist</returns>
        public bool CreateRegion(string regionName)
        {
            if (IsRegionSupported)
                return this.CreateRegionImpl(regionName);

            throw new NotSupportedException(string.Format("Regions are not supported in this Provider {0}", this.Name));
        }

        protected virtual bool CreateRegionImpl(string regionName)
        {
            return false;
        }

        public bool RemoveRegion(string regionName)
        {
            if (IsRegionSupported)
                return this.RemoveRegionImpl(regionName);

            throw new NotSupportedException(string.Format("Regions are not supported in this Provider {0}", this.Name));
        }

        protected virtual bool RemoveRegionImpl(string regionName)
        {
            return false;
        }

        public bool ClearRegion(string regionName)
        {
            if (IsRegionSupported)
                return this.ClearRegionImpl(regionName);

            throw new NotSupportedException(string.Format("Regions are not supported in this Provider {0}", this.Name));
        }

        protected virtual bool ClearRegionImpl(string regionName)
        {
            return false;
        }

        #endregion

        #region A P P F A B R I C  S P E C I F I C   M E T H O D S

        public void ResetObjectTimeout(string key, TimeSpan newTimeout)
        {
            //Using IsRegionSupported property to make sure that it can be used
            //only by AppFabric provider as it is specific to the same.
            if (IsRegionSupported)
            {
                this.ResetObjectTimeoutImpl(key, newTimeout);
                return;
            }

            throw new NotSupportedException(string.Format("This Method is not supported by this Provider {0}", this.Name));
        }

        protected virtual void ResetObjectTimeoutImpl(string key, TimeSpan newTimeout)
        {
            //Intentionally left blank
        }

        public void ResetObjectTimeout(string key, TimeSpan newTimeout, string regionName)
        {
            //TODO: Currently, using IsRegionSupported property here as well.  Need to analyze 
            if (IsRegionSupported)
            {
                this.ResetObjectTimeoutImpl(key, newTimeout, regionName);
                return;
            }

            throw new NotSupportedException(string.Format("This Method is not supported by this Provider {0}", this.Name));
        }

        protected virtual void ResetObjectTimeoutImpl(string key, TimeSpan newTimeout, string regionName)
        {
            //Intentionally left blank
        }


        /// <summary>
        /// Sets a Named Cache if supported by the provider.
        /// </summary>
        /// <param name="cacheName"></param>
        public void SetNamedCache(string cacheName)
        {
            if (IsNamedCacheSupported)
            {
                this.SetNamedCacheImpl(cacheName);
                return;
            }

            throw new NotSupportedException();
        }

        protected virtual void SetNamedCacheImpl(string cacheName)
        {
            //Intentionally left blank.
        }

        public void SetDefaultCache()
        {
            this.SetNamedCache(string.Empty);
        }

        public void NotifyCacheServer(bool clientClosing)
        {
            this.NotifyCacheServerImpl(clientClosing);
        }

        protected virtual void NotifyCacheServerImpl(bool clientClosing)
        {
            //Intentionally left blank
            //Can be overriden by Specific Cache Providers.
        }



        #endregion

        /// <summary>
        ///     This method can be overrid
        /// </summary>
        protected virtual void BootstrapContainer() { }

        protected virtual void UpdateDependencyTrackerCollection()
        {

        }

        private void InitializeContainer()
        {
            var iocFactory = new DIContainerFactory();
            var assemblyResourceName = "assembly://" + Assembly.GetExecutingAssembly().GetName().Name + "/WindsorConfiguration.xml";
            this.DIContainer = iocFactory.GetDIContainerProvider(DIContainerType.WindsorCastle, assemblyResourceName);
            this.DIContainer.RegisterType(typeof(CacheProvider), this);
        }

        private void InitializeDependencyTrackerKey()
        {
            var applicationName = ConfigurationManager.AppSettings["ApplicationName"];
            var macAddress = this.GetMACAddress();
            this.CacheClientNodeIdentifier = string.IsNullOrEmpty(applicationName) ? macAddress
                             : string.Format("{0}_{1}", applicationName, macAddress);
        }

        protected string GetMACAddress()
        {
            var macAddress = string.Empty;
            var managementObjectSearcher = new ManagementObjectSearcher("Select * from Win32_NetworkAdapterConfiguration");
            var managementObjectCollection = managementObjectSearcher.Get();
            foreach (ManagementObject managementObject in managementObjectCollection)
            {
                if (managementObject["MacAddress"] == null) continue;
                macAddress = managementObject["MacAddress"].ToString();
                //macAddress = macAddress.Replace(":", "");
                break;
            }
            return macAddress;
        }

    }
}
